package com.kakarote.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.Browser;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.kakarote.admin.entity.BO.AdminDeleteByBatchIdBO;
import com.kakarote.admin.entity.BO.RenameFileBO;
import com.kakarote.admin.entity.PO.AdminFile;
import com.kakarote.admin.mapper.AdminFileMapper;
import com.kakarote.admin.service.IAdminFileService;
import com.kakarote.common.upload.entity.UploadEntity;
import com.kakarote.common.upload.service.FileService;
import com.kakarote.core.common.enums.SystemCodeEnum;
import com.kakarote.core.exception.CrmException;
import com.kakarote.core.feign.admin.entity.FileEntity;
import com.kakarote.core.servlet.BaseServiceImpl;
import com.kakarote.core.utils.BaseUtil;
import com.kakarote.core.utils.UserCacheUtil;
import com.kakarote.core.utils.UserUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 附件表 服务实现类
 * </p>
 *
 * @author zhangzhiwei
 * @since 2020-04-27
 */
@Service
public class AdminFileServiceImpl extends BaseServiceImpl<AdminFileMapper, AdminFile> implements IAdminFileService {
    /**
     * 公共文件的bucket
     */
    private static final String PUBLIC_BUCKET_NAME_KEY = "publicBucket";
    private final static List<String> videoType = ListUtil.toList("mp4", "webm", "ogg", "mov");

    @Autowired
    private FileService fileService;


    /**
     * 上传文件
     *
     * @param file    文件对象
     * @param batchId batchId
     * @param type    type
     * @return entity
     */
    @Override
    public com.kakarote.core.entity.UploadEntity upload(MultipartFile file, String batchId, String type, Integer isPublic) {
        if (StrUtil.isEmpty(batchId)) {
            batchId = IdUtil.simpleUUID();
        }
        Long fileId = BaseUtil.getNextId();
        com.kakarote.core.entity.UploadEntity entity = upload(file, isPublic, file.getOriginalFilename(), fileId);
        entity.setIsPublic(isPublic);
        AdminFile adminFile = buildFile(entity, fileId, batchId, isPublic, type);
        entity.setBatchId(batchId);
        save(adminFile);
        return entity;
    }

    /**
     * 上传临时文件，此文件会七天后删除
     *
     * @param file    文件对象
     * @param batchId batchId
     * @return entity
     */
    @Override
    public com.kakarote.core.entity.UploadEntity uploadTempFile(MultipartFile file, String batchId) {
        UploadEntity entity = new UploadEntity();
        entity.setFileId(BaseUtil.getNextId().toString());
        entity.setName(file.getOriginalFilename());
        entity.setSize(file.getSize());
        try {
            fileService.uploadTempFile(file.getInputStream(), entity);
        } catch (Exception ex) {
            throw new CrmException(SystemCodeEnum.SYSTEM_UPLOAD_FILE_ERROR);
        }
        return BeanUtil.copyProperties(entity, com.kakarote.core.entity.UploadEntity.class);
    }

    /**
     * 单一文件上传
     *
     * @param file:文件体
     * @param batchId:文件上传流
     * @param type:类型
     * @param isPublic:isPublic
     * @return 文件属性类
     */
    @Override
    public com.kakarote.core.entity.UploadEntity uploadBySingle(MultipartFile file, String batchId, String type, Integer isPublic) {
        String fileName;
        Long fileId;
        if (StrUtil.isEmpty(batchId)) {
            batchId = IdUtil.simpleUUID();
            fileName = file.getOriginalFilename();
            fileId = BaseUtil.getNextId();
        } else {
            AdminFile adminFile = this.lambdaQuery().eq(AdminFile::getBatchId, batchId).one();
            if (adminFile != null) {
                fileName = adminFile.getName();
                fileId = adminFile.getFileId();
            } else {
                batchId = IdUtil.simpleUUID();
                fileName = file.getOriginalFilename();
                fileId = BaseUtil.getNextId();
            }
        }

        com.kakarote.core.entity.UploadEntity entity = upload(file, isPublic, fileName, fileId);
        AdminFile adminFile = buildFile(entity, fileId, batchId, isPublic, type);
        saveOrUpdate(adminFile);
        entity.setBatchId(batchId);
        return entity;

    }

    /**
     * 上传文件到文件存储服务
     *
     * @param file     文件对象
     * @param isPublic 是否公开文件
     * @param fileName 文件名称
     * @param fileId   文件ID
     * @return entity
     */
    private com.kakarote.core.entity.UploadEntity upload(MultipartFile file, Integer isPublic, String fileName, Long fileId) {
        UploadEntity entity = new UploadEntity();
        entity.setFileId(fileId.toString());
        entity.setName(fileName);
        entity.setSize(file.getSize());
        try {
            fileService.uploadFile(file.getInputStream(), entity, getBucketName(isPublic));
            if (Objects.equals(isPublic, com.kakarote.core.entity.UploadEntity.PUBLIC_KEY)) {
                entity.setPath(entity.getUrl());
            }
        } catch (Exception ex) {
            throw new CrmException(SystemCodeEnum.SYSTEM_UPLOAD_FILE_ERROR);
        }
        if (com.kakarote.core.entity.UploadEntity.PRIVATE_KEY.equals(isPublic)) {
            entity.setUrl(null);
        }
        return BeanUtil.copyProperties(entity, com.kakarote.core.entity.UploadEntity.class);
    }

    /**
     * 构建adminFile对象
     *
     * @param entity   上传文件entity
     * @param fileId   文件ID
     * @param batchId  batchId
     * @param isPublic 是否公开文件
     * @param type     文件类型
     * @return adminFile
     */
    private AdminFile buildFile(com.kakarote.core.entity.UploadEntity entity, Long fileId, String batchId, Integer isPublic, String type) {
        AdminFile adminFile = new AdminFile();
        adminFile.setFileId(fileId);
        adminFile.setName(entity.getName());
        adminFile.setSize(entity.getSize());
        adminFile.setPath(entity.getPath());
        adminFile.setBatchId(batchId);
        if (StrUtil.isEmpty(type)) {
            type = "file";
        }
        adminFile.setFileType(type);
        adminFile.setType(fileService.getType().name());
        adminFile.setIsPublic(isPublic);
        adminFile.setCreateTime(LocalDateTime.now());
        adminFile.setCreateUserId(UserUtil.getUserId());
        return adminFile;
    }

    /**
     * 获取文件列表
     *
     * @param batchId batchId
     * @return data
     */
    @Override
    public List<FileEntity> queryFileList(String batchId) {
        if (StrUtil.isEmpty(batchId)) {
            return new ArrayList<>();
        }
        List<AdminFile> list = lambdaQuery().eq(AdminFile::getBatchId, batchId).orderByAsc(AdminFile::getFileId).list();
        return list.stream().map(this::parseFileEntity).collect(Collectors.toList());
    }

    /**
     * 获取文件列表
     *
     * @param batchIdList batchIdList
     * @return data
     */
    @Override
    public List<FileEntity> queryFileList(List<String> batchIdList) {
        if (batchIdList.size() == 0) {
            return new ArrayList<>();
        }
        List<AdminFile> list = lambdaQuery().in(AdminFile::getBatchId, batchIdList).list();
        return list.stream().map(this::parseFileEntity).collect(Collectors.toList());
    }

    /**
     * 格式化文件信息
     *
     * @param adminFile 文件对象
     */
    private FileEntity parseFileEntity(AdminFile adminFile) {
        FileEntity entity = new FileEntity();
        if (adminFile == null) {
            return entity;
        }
        BeanUtil.copyProperties(adminFile, entity);
        entity.setCreateUserName(UserCacheUtil.getUserName(adminFile.getCreateUserId()));
        return entity;
    }

    /**
     * 通过文件ID查询
     *
     * @param fileId 文件ID
     * @return data
     */
    @Override
    public FileEntity queryById(Long fileId) {
        AdminFile adminFile = getById(fileId);
        return parseFileEntity(adminFile);
    }

    /**
     * 获取单个文件
     *
     * @param batchId batchId
     * @return data
     */
    @Override
    public FileEntity queryOneByBatchId(String batchId) {
        AdminFile adminFile = lambdaQuery().eq(AdminFile::getBatchId, batchId).one();
        return parseFileEntity(adminFile);
    }

    /**
     * 通过文件ID删除
     *
     * @param fileId 文件ID
     */
    @Override
    public AdminFile deleteById(Long fileId) {
        AdminFile adminFile = getById(fileId);
        if (adminFile != null) {
            fileService.deleteFile(adminFile.getPath(), getBucketName(adminFile.getIsPublic()));
            removeById(fileId);
        }
        return adminFile;
    }

    /**
     * 通过batchId删除
     *
     * @param batchId batchId
     */
    @Override
    public void deleteByBatchId(List<String> batchId) {
        if (batchId.isEmpty()) {
            return;
        }
        List<AdminFile> fileList = lambdaQuery().select(AdminFile::getFileId).in(AdminFile::getBatchId, batchId).list();
        List<Long> fileIdList = fileList.stream().map(AdminFile::getFileId).collect(Collectors.toList());
        fileIdList.forEach(this::deleteById);
    }

    /**
     * 下载文件
     *
     * @param response response
     * @param fileId   fileId
     */
    @Override
    public AdminFile down(HttpServletResponse response, Long fileId) {
        AdminFile adminFile = getById(fileId);
        if (adminFile != null) {
            UploadEntity entity = new UploadEntity();
            entity.setPath(adminFile.getPath());
            InputStream inputStream = fileService.downFile(entity, getBucketName(adminFile.getIsPublic()));
            if (inputStream != null) {
                HttpServletRequest request = BaseUtil.getRequest();
                //判断是否是ios浏览器
                String userAgent = ServletUtil.getHeaderIgnoreCase(request, "User-Agent");
                UserAgent parse = UserAgentUtil.parse(userAgent);
                Browser browser = parse.getBrowser();
                if (browser.getName().equals("Safari")&&videoType.contains(FileUtil.extName(adminFile.getName()))) {
                    File file = new File(adminFile.getName());
                    OutputStream outputStream;
                    try {
                        outputStream = new FileOutputStream(file);
                        int len;
                        byte[] buf = new byte[1024];
                        while ((len = inputStream.read(buf, 0, 1024)) != -1) {
                            outputStream.write(buf, 0, len);
                        }
                        outputStream.flush();
                        outputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    try {
                        sendVideo(request, response, file, adminFile.getName());
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }finally {
                        //删除临时文件
                        file.delete();
                    }
                    return adminFile;
                }
                final String contentType = ObjectUtil.defaultIfNull(FileUtil.getMimeType(adminFile.getName()), "application/octet-stream");
                response.setHeader("Content-Disposition", StrUtil.format("attachment;filename={}", URLUtil.encodeAll(adminFile.getName())));
                response.setContentType(contentType);
                ServletUtil.write(response, inputStream);
            }
        }
        return adminFile;
    }

    @Override
    public void getFileStreamByPath(UploadEntity upload, HttpServletResponse response) {
        InputStream inputStream = fileService.downFile(upload);
        if (inputStream != null) {
            final String contentType = "application/octet-stream;charset=utf-8";
            ServletUtil.write(response, inputStream, contentType);
        }
    }

    @Override
    public String getFileBaseById(Long fileId, HttpServletResponse response) throws IOException {
        AdminFile adminFile = getById(fileId);
        if (adminFile != null) {
            UploadEntity entity = new UploadEntity();
            entity.setPath(adminFile.getPath());
            InputStream inputStream = fileService.downFile(entity, getBucketName(adminFile.getIsPublic()));
            if (inputStream != null) {
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int length;
                while ((length = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, length);
                }
                byte[] data = outputStream.toByteArray();
                String base64String = Base64.getEncoder().encodeToString(data);
                return "data:image/png;base64," + base64String;
            }
            return null;
        }else {
            return null;
        }
    }

    @Override
    public void renameFileById(RenameFileBO renameFileBO) {
        lambdaUpdate().set(AdminFile::getName, renameFileBO.getName())
                .eq(AdminFile::getFileId, renameFileBO.getFileId()).update();
    }

    /**
     * 查询文件数量
     *
     * @param batchId batchId
     * @return num
     */
    @Override
    public Integer queryNum(List<String> batchId) {
        if (batchId.size() == 0) {
            return 0;
        }
        return lambdaQuery().in(AdminFile::getBatchId, batchId).count().intValue();
    }

    /**
     * 复制文件
     *
     * @param batchId 文件batchId
     * @return 新的batchId
     */
    @Override
    public String copyFile(String batchId) {
        String newBatchId = IdUtil.simpleUUID();
        if (StrUtil.isEmpty(batchId)) {
            return newBatchId;
        }
        List<AdminFile> fileList = lambdaQuery().eq(AdminFile::getBatchId, batchId).list();
        for (AdminFile adminFile : fileList) {
            adminFile.setFileId(BaseUtil.getNextId());
            adminFile.setBatchId(newBatchId);
        }
        saveBatch(fileList);
        return newBatchId;
    }


    @Override
    public List<FileEntity> queryByIds(Collection<Long> fileIds) {
        if (CollUtil.isEmpty(fileIds)) {
            return new ArrayList<>();
        }
        List<AdminFile> fileList = lambdaQuery().in(AdminFile::getFileId, fileIds).list();
        return fileList.stream().map(this::parseFileEntity).collect(Collectors.toList());
    }

    @Override
    public void deleteByBatchId(AdminDeleteByBatchIdBO deleteByBatchIdBO) {
        Integer type = deleteByBatchIdBO.getType();
        // 1 附件 2 图片
        int fType = 1;
        int imgType = 2;
        String fileType = "";
        if (Objects.equals(fType, type)) {
            fileType = "file";
        } else if (Objects.equals(imgType, type)) {
            fileType = "img";
        }
        List<AdminFile> fileList = lambdaQuery().select(AdminFile::getFileId).eq(AdminFile::getBatchId, deleteByBatchIdBO.getBatchId())
                .eq(StrUtil.isNotEmpty(fileType), AdminFile::getFileType, fileType).list();
        List<Long> fileIdList = fileList.stream().map(AdminFile::getFileId).collect(Collectors.toList());
        fileIdList.forEach(this::deleteById);
    }

    @Override
    public void updateFileBatchId(String oldBatchId, String newBatchId) {
        List<AdminFile> fileList = lambdaQuery().select(AdminFile::getFileId).eq(AdminFile::getBatchId, oldBatchId).list();
        if (!fileList.isEmpty()) {
            fileList.forEach(adminFile -> {
                adminFile.setBatchId(newBatchId);
            });
            updateBatchById(fileList);
        }
    }

    /**
     * 获取公共存储桶名称
     *
     * @return bucketName
     */
    private String getBucketName(Integer isPublic) {
        if (Objects.equals(isPublic, com.kakarote.core.entity.UploadEntity.PUBLIC_KEY)) {
            Object o = fileService.getProperties().getExtra().get(PUBLIC_BUCKET_NAME_KEY);
            if (o instanceof String) {
                return (String) o;
            }
        }
        return fileService.getProperties().getBucketName();
    }



    /**
     * Safari浏览器播放视频特殊处理
     * @param request
     * @param response
     * @param file
     * @param fileName
     * @throws IOException
     */
    private void sendVideo(HttpServletRequest request, HttpServletResponse response, File file, String fileName) throws IOException {
        RandomAccessFile randomFile = new RandomAccessFile(file, "r");//只读模式
        long contentLength = randomFile.length();
        String range = request.getHeader("Range");
        int start = 0, end = 0;
        if (range != null && range.startsWith("bytes=")) {
            String[] values = range.split("=")[1].split("-");
            start = Integer.parseInt(values[0]);
            if (values.length > 1) {
                end = Integer.parseInt(values[1]);
            }
        }
        int requestSize = 0;
        if (end != 0 && end > start) {
            requestSize = end - start + 1;
        } else {
            requestSize = Integer.MAX_VALUE;
        }

        response.setContentType("video/mp4");
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("ETag", fileName);
        response.setHeader("Last-Modified", new Date().toString());
        //第一次请求只返回content length来让客户端请求多次实际数据
        if (range == null) {
            response.setHeader("Content-length", contentLength + "");
        } else {
            //以后的多次以断点续传的方式来返回视频数据
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);//206
            long requestStart = 0, requestEnd = 0;
            String[] ranges = range.split("=");
            if (ranges.length > 1) {
                String[] rangeDatas = ranges[1].split("-");
                requestStart = Integer.parseInt(rangeDatas[0]);
                if (rangeDatas.length > 1) {
                    requestEnd = Integer.parseInt(rangeDatas[1]);
                }
            }
            long length = 0;
            if (requestEnd > 0) {
                length = requestEnd - requestStart + 1;
                response.setHeader("Content-length", "" + length);
                response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength);
            } else {
                length = contentLength - requestStart;
                response.setHeader("Content-length", "" + length);
                response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1) + "/" + contentLength);
            }
        }
        ServletOutputStream out = response.getOutputStream();
        int needSize = requestSize;
        randomFile.seek(start);
        while (needSize > 0) {
            byte[] buffer = new byte[4096];
            int len = randomFile.read(buffer);
            if (needSize < buffer.length) {
                out.write(buffer, 0, needSize);
            } else {
                out.write(buffer, 0, len);
                if (len < buffer.length) {
                    break;
                }
            }
            needSize -= buffer.length;
        }
        randomFile.close();
        out.close();
    }

}
